package com.aboverock.core.shiro.authz.rest;

import com.aboverock.core.shiro.util.ShiroRestUrlUtil;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.shiro.util.AntPathMatcher;
import org.apache.shiro.util.PatternMatcher;
import org.apache.shiro.web.filter.mgt.FilterChainManager;
import org.apache.shiro.web.filter.mgt.FilterChainResolver;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * The Class CustomPathMatchingFilterChainResolver.
 *
 * @author Rock Wang
 */
public class RestPathMatchingFilterChainResolver implements FilterChainResolver {

    private static transient final Logger log = LoggerFactory
            .getLogger(RestPathMatchingFilterChainResolver.class);

    private FilterChainManager filterChainManager;

    private PatternMatcher pathMatcher;

    public RestPathMatchingFilterChainResolver() {
        this.pathMatcher = new AntPathMatcher();
        this.filterChainManager = new RestFilterChainManager();
    }

    public RestPathMatchingFilterChainResolver(FilterChainManager filterChainManager) {
        this.pathMatcher = new AntPathMatcher();
        this.filterChainManager = filterChainManager;
    }

    public PatternMatcher getPathMatcher() {
        return pathMatcher;
    }

    public void setPathMatcher(PatternMatcher pathMatcher) {
        this.pathMatcher = pathMatcher;
    }

    public FilterChainManager getFilterChainManager() {
        return filterChainManager;
    }

    public void setFilterChainManager(FilterChainManager filterChainManager) {
        this.filterChainManager = filterChainManager;
    }

    @Override
    public FilterChain getChain(ServletRequest request, ServletResponse response,
            FilterChain originalChain) {
        FilterChainManager filterChainManager = getFilterChainManager();
        if (!filterChainManager.hasChains()) {
            return null;
        }

        String requestURI = getPathWithinApplication(request, false);
        String requestURIWithMethod = getPathWithinApplication(request, true);
        String chainName = null;

        // the 'chain names' in this implementation are actually path patterns defined by the user.
        // We just use them
        // as the chain name for the FilterChainManager's requirements
        for (String pathPattern : filterChainManager.getChainNames()) {

            // If the path does match, then pass on to the subclass implementation for specific
            // checks:
            if (pathMatches(pathPattern, requestURI, requestURIWithMethod)) {
                if (null == chainName || pathMatches(chainName, pathPattern)) {
                    chainName = pathPattern;
                }
            }
        }

        if (null == chainName) {
            return null;
        }

        if (log.isTraceEnabled()) {
            log.trace("Matched path pattern [" + chainName + "] for requestURI [" + requestURI
                    + "].  " + "Utilizing corresponding filter chain...");
        }
        return filterChainManager.proxy(originalChain, chainName);
    }

    private boolean pathMatches(String pattern, String path, String pathWithMethod) {
        PatternMatcher pathMatcher = getPathMatcher();
        if (ShiroRestUrlUtil.isRestUrl(pattern)) {
            return pathMatcher.matches(pattern, pathWithMethod);
        }
        return pathMatcher.matches(pattern, path);
    }

    private boolean pathMatches(String pattern, String path) {
        PatternMatcher pathMatcher = getPathMatcher();
        if (ShiroRestUrlUtil.isRestUrl(pattern)) {
            return pathMatcher.matches(pattern, path);
        }
        return pathMatcher.matches(pattern, ShiroRestUrlUtil.getRawUrl(path));
    }

    protected String getPathWithinApplication(ServletRequest request, boolean getMethod) {
        if (getMethod) {
            return ShiroRestUrlUtil.buildRestUrl(WebUtils.toHttp(request).getMethod(),
                    WebUtils.getPathWithinApplication(WebUtils.toHttp(request)));
        }
        return WebUtils.getPathWithinApplication(WebUtils.toHttp(request));
    }
}
